package app

import (
	"bufio"
	"encoding/json"
	"fmt"
	"github.com/go-resty/resty/v2"
	"log/slog"
	"npm/utils"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

type DownAll struct {
	dir             string
	dependenciesUrl []string
}

func DownAllRun(dir string) error {
	down := &DownAll{dir: dir}
	return down.Start()
}

func (d *DownAll) Start() error {
	d.ParsePackageJSONFile()
	d.WriteDependenciesUrls()
	d.Download()
	return nil
}

func (d *DownAll) Download() {
	downReader := bufio.NewReader(os.Stdin)
	fmt.Print("依赖解析完成。是否开始下载？输入：(y) 立即开始下载 (n) 退出程序。\n: ")
	confirm, _ := downReader.ReadString('\n')
	confirm = strings.TrimSpace(confirm)
	if confirm != "y" {
		return
	}

	//start downloads
	var wg sync.WaitGroup
	semaphore := make(chan struct{}, utils.Conf.MaxConcurrentRequests)
	downloadDir := fmt.Sprintf("%s/registry", utils.GetCurrentDir())
	for _, url := range d.dependenciesUrl {
		semaphore <- struct{}{}
		wg.Add(1)
		go utils.Down(downloadDir, url, &wg, semaphore)
	}
	wg.Wait()
}

func (d *DownAll) WriteDependenciesUrls() {
	//write urls file
	filename := utils.SanitizeFileName(d.dir)
	logFilePath := fmt.Sprintf("%s/%s_%s.log", utils.GetCurrentDir(), filename, time.Now().Format("2006-01-02_150405"))
	file, logErr := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	defer file.Close() // 确保在函数结束时关闭文件

	//write logs
	for _, url := range d.dependenciesUrl {
		if logErr == nil {
			_, _ = file.WriteString(url + "\n") // 添加换行符以分隔内容
		}
	}
}

func (d *DownAll) ParsePackageJSONFile() {
	if d.dir == "" {
		return
	}

	if !utils.CheckFileExistence(d.dir) {
		slog.Error("文件没有找到", "path", d.dir)
		return
	}

	dependencies, fErr := d.FindPackageJsonFiles(d.dir)
	if fErr != nil {
		slog.Error("package.json 查找失败", "error", fErr)
		os.Exit(1)
	}

	for k, v := range dependencies {
		d.FetchVersions(k, v)
	}
}

func (d *DownAll) FetchVersions(packageName, version string) {
	slog.Info(fmt.Sprintf("解析: %s 依赖关系...", packageName))
	urlStr := fmt.Sprintf("%s%s", utils.Conf.Registry, packageName)
	client := resty.New()
	client.SetHeaders(map[string]string{
		"Content-Type": "application/json",
		"User-Agent":   utils.Conf.UserAgent,
	})
	resp, err := client.R().EnableTrace().Get(urlStr)
	if err != nil {
		slog.Error("依赖信息请求失败:", "Error", err)
		return
	}

	body := resp.Body()
	versionInfo := utils.DependenciesInfo{}
	err = json.Unmarshal(body, &versionInfo)
	if err != nil {
		slog.Error("依赖版本信息解析失败:", "Error", err)
		return
	}

	if versionInfo.ErrorMessage != "" {
		slog.Error("依赖信息读取失败:", "Error", versionInfo.ErrorMessage)
		return
	}

	versionKeys := utils.GetKeys1(versionInfo.Time)
	var versions []string
	for _, ver := range versionKeys {
		if utils.IsAllEnglishLetters(ver) {
			continue
		}

		versions = append(versions, ver)
	}

	for _, ver := range versions {
		downVer := versionInfo.Versions[ver]
		fileUrl := downVer.Dist.Tarball

		if utils.Conf.Versions.OnlyRelease && utils.ContainsLetter(ver) {
			continue
		} else if !utils.Conf.Versions.OnlyRelease && utils.ContainsSpecialKeywords(ver, utils.Conf.Versions.ExcludeVersionKeywords) {
			continue
		}

		d.dependenciesUrl = append(d.dependenciesUrl, fileUrl)
	}
}

func (d *DownAll) FindPackageJsonFiles(path string) (map[string]string, error) {
	dependencies := map[string]string{}
	// 从当前目录开始遍历
	err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// 检查文件名是否为 package.json
		if info.IsDir() || (!info.IsDir() && info.Name() != "package.json") {
			return nil
		}

		jsonBody, jsonErr := os.ReadFile(path)
		if jsonErr == nil {
			slog.Error("文件读取失败", "path", path, "err", jsonErr)
			return nil
		}

		packageInfo := utils.PackageJson{}
		jErr := json.Unmarshal(jsonBody, &packageInfo)
		if jErr == nil {
			slog.Error("package.json 文件解析失败", "path", path, "err", jsonErr)
			return nil
		}

		for name, v := range packageInfo.Dependencies {
			npmVer, _ := utils.ParseVersion(name, v)
			if npmVer != "" {
				dependencies[npmVer] = v
			} else {
				dependencies[name] = v
			}
		}

		for name, v := range packageInfo.DevDependencies {
			npmVer, _ := utils.ParseVersion(name, v)
			if npmVer != "" {
				dependencies[npmVer] = v
			} else {
				dependencies[name] = v
			}
		}

		for name, v := range packageInfo.PeerDependencies {
			npmVer, _ := utils.ParseVersion(name, v)
			if npmVer != "" {
				dependencies[npmVer] = v
			} else {
				dependencies[name] = v
			}
		}

		for name, v := range packageInfo.BundledDependencies {
			npmVer, _ := utils.ParseVersion(name, v)
			if npmVer != "" {
				dependencies[npmVer] = v
			} else {
				dependencies[name] = v
			}
		}

		for name, v := range packageInfo.OptionalDependencies {
			npmName, _ := utils.ParseVersion(name, v)
			if npmName == "" {
				npmName = name
			}

			if _, exist := dependencies[npmName]; !exist {
				dependencies[npmName] = v
			} else {
				dependencies[npmName] = v
			}
		}

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("遍历目录时出错: %v", err)
	}

	return dependencies, nil
}
